package com.workcell.workdata.im.repository;


import com.workcell.workdata.im.entity.*;
import com.workcell.workdata.im.constant.Common;
import com.workcell.workdata.im.constant.MessageAction;
import com.workcell.workdata.im.repository.mapper.AppMessageMapper;
import com.workcell.workdata.im.repository.mapper.GroupMessageMapper;
import com.workcell.workdata.im.repository.mapper.SystemMessageMapper;
import org.springframework.data.domain.*;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;

@Component
public class MessageLookupRepositoryProxy {

    @Resource
    private MessageRepositoryProxy messageRepositoryProxy;

    @Resource
    private GroupMessageLookupRepository groupMessageLookupRepository;

    @Resource
    private AppMessageLookupRepository appMessageLookupRepository;

    @Resource
    private ChatMessageLookupRepository chatMessageLookupRepository;

    @Resource
    private SystemMessageLookupRepository systemMessageLookupRepository;

    public List<Message> findList(List<MessageIndex> indices) {

        List<Message> messageList = new LinkedList<>(messageRepositoryProxy.findList(indices, MessageAction.ACTION_0));

        messageList.addAll(messageRepositoryProxy.findList(indices,MessageAction.ACTION_2));

        messageList.addAll(messageRepositoryProxy.findList(indices,MessageAction.ACTION_3));

        messageList.addAll(messageRepositoryProxy.findList(indices,MessageAction.ACTION_4));

        messageList.addAll(messageRepositoryProxy.findList(indices,MessageAction.ACTION_200));

        messageList.addAll(messageRepositoryProxy.findList(indices,null));

        messageList.sort(Comparator.comparingLong(o -> o.getCreateTime().getTime()));

        return messageList;
    }

    public Page<Message> findListOfGroup(long groupId , long fromId){

        Pageable pageable = PageRequest.of(0, Common.API_PAGE_SIZE, Sort.by(Sort.Order.desc("id")));

        Page<Message> page = groupMessageLookupRepository.findAll(new GroupSpecification(groupId,fromId),pageable);

        List<Message> mappedList = page.get().map(new GroupMessageMapper()::view).collect(Collectors.toList());

        return new PageImpl<>(mappedList,pageable,page.getTotalElements());

    }

    public Page<Message> findListOfApp(long appId , long fromId){

        Pageable pageable = PageRequest.of(0, Common.API_PAGE_SIZE, Sort.by(Sort.Order.desc("id")));

        Page<Message> page = appMessageLookupRepository.findAll(new AppSpecification(appId,fromId),pageable);

        List<Message> mappedList = page.get().map(new AppMessageMapper()::view).collect(Collectors.toList());

        return new PageImpl<>(mappedList,pageable,page.getTotalElements());

    }

    public Page<Message> findListOfSystem(long fromId){

        Pageable pageable = PageRequest.of(0, Common.API_PAGE_SIZE, Sort.by(Sort.Order.desc("id")));

        Page<Message> page = systemMessageLookupRepository.findAll(new SystemSpecification(fromId),pageable);

        List<Message> mappedList = page.get().map(new SystemMessageMapper()::view).collect(Collectors.toList());

        return new PageImpl<>(mappedList,pageable,page.getTotalElements());

    }


    public Page<Message> findListOfFriend(long uid ,long fid, long fromId){

        Pageable pageable = PageRequest.of(0, Common.API_PAGE_SIZE, Sort.by(Sort.Order.desc("id")));

        return chatMessageLookupRepository.findAll(new P2pSpecification(uid,fid,fromId),pageable);
    }

    private static class GroupSpecification implements Specification<GroupMessage> {

        private final long groupId;

        private final long fromId;

        private GroupSpecification(long groupId,long fromId){
            this.fromId = fromId;
            this.groupId = groupId;
        }

        @Override
        public Predicate toPredicate(Root<GroupMessage> root, CriteriaQuery<?> query, CriteriaBuilder builder) {

            Predicate idPredicate = fromId <= 0 ?  builder.isNotNull(root.get("id")) :  builder.lt(root.get("id").as(Long.class), fromId);

            query.where(idPredicate,
                    builder.equal(root.get("groupId").as(Long.class), groupId));
            return query.getRestriction();
        }
    }

    private static class P2pSpecification implements Specification<ChatMessage> {

        private final long uid;
        private final long fid;

        private final long fromId;

        private P2pSpecification(long uid,long fid,long fromId){
            this.fid = fid;
            this.uid = uid;
            this.fromId = fromId;
        }
        @Override
        public Predicate toPredicate(Root<ChatMessage> root, CriteriaQuery<?> query, CriteriaBuilder builder) {

            Predicate idPredicate = fromId <= 0 ?  builder.isNotNull(root.get("id")) :  builder.lt(root.get("id").as(Long.class), fromId);

            Predicate sendPredicate = builder.and(builder.equal(root.get("sender").as(Long.class), uid),builder.equal(root.get("receiver").as(Long.class), fid));
            Predicate recvPredicate = builder.and(builder.equal(root.get("receiver").as(Long.class), uid),builder.equal(root.get("sender").as(Long.class), fid));

            query.where(builder.and(idPredicate,builder.or(sendPredicate,recvPredicate)));
            return query.getRestriction();
        }
    }

    private static class SystemSpecification implements Specification<SystemMessage> {

        private final long fromId;

        private SystemSpecification(long fromId){
            this.fromId = fromId;
        }

        @Override
        public Predicate toPredicate(Root<SystemMessage> root, CriteriaQuery<?> query, CriteriaBuilder builder) {

            Predicate idPredicate = fromId <= 0 ?  builder.isNotNull(root.get("id")) :  builder.lt(root.get("id").as(Long.class), fromId);

            query.where(idPredicate);
            return query.getRestriction();
        }
    }

    private static class AppSpecification implements Specification<AppMessage> {

        private final long appId;

        private final long fromId;

        private AppSpecification(long appId,long fromId){
            this.fromId = fromId;
            this.appId = appId;
        }

        @Override
        public Predicate toPredicate(Root<AppMessage> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
            Predicate idPredicate = fromId <= 0 ?  builder.isNotNull(root.get("id")) :  builder.lt(root.get("id").as(Long.class), fromId);
            query.where(idPredicate, builder.equal(root.get("appId").as(Long.class), appId));
            return query.getRestriction();
        }
    }
}
